﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace tiDE
{
    public class Linker
    {
        public LinkerOutput Link(List<AssemblyOutput> Output)
        {
            List<byte> finalOutput = new List<byte>();
            List<LinkError> errors = new List<LinkError>();
            Dictionary<string, ushort> Labels = new Dictionary<string,ushort>();
            List<Reference> References = new List<Reference>();
            Output.Sort(new PageSort());
            foreach (AssemblyOutput output in Output)
            {
                ushort offset = (ushort)finalOutput.Count;
                for (int i = 0; i < output.Labels.Count; i++)
                {
                    output.Labels[output.Labels.ElementAt(i).Key] += offset; // Offset all label values
                    // TODO: Check for incorrect labels
                    if (!Labels.ContainsKey(output.Labels.ElementAt(i).Key))
                        Labels.Add(output.Labels.ElementAt(i).Key, output.Labels.ElementAt(i).Value); // Add to master list
                    else
                        errors.Add(new LinkError()
                        {
                            Message = "Duplicate label found: " + output.Labels.ElementAt(i).Key,
                        });
                }
                for (int i = 0; i < output.References.Count; i++)
                {
                    output.References[i].Value += offset; // Offset all reference values
                    // TODO: Check for incorrect references
                    References.Add(new Reference(output.References[i].Key, output.References[i].Value)); // Add to master list
                }
                if (finalOutput.Count / 0x4000 != output.Page)
                {
                    int numToAdd = (output.Page * 0x4000) - finalOutput.Count;
                    for (int i = 0; i < numToAdd; i++)
                        finalOutput.Add(0xFF);
                }
                finalOutput.AddRange(output.Output); // Merge all output
            }
            foreach (Reference pair in References)
            {
                try
                {
                    finalOutput[(int)pair.Value] += (byte)(Labels[pair.Key] % 256);
                    finalOutput[(int)pair.Value + 1] += (byte)(Labels[pair.Key] / 256); // Fix all references
                }
                catch
                {
                    errors.Add(new LinkError()
                    {
                        Message = pair.Key + " does not exist",
                    });
                }
            }
            return new LinkerOutput()
            {
                FinalOutput = finalOutput.ToArray(),
                Errors = errors,
            };
        }
    }

    public class LinkerOutput
    {
        public byte[] FinalOutput { get; set; }
        public List<LinkError> Errors { get; set; }
    }

    public class PageSort : IComparer<AssemblyOutput>
    {
        public int Compare(AssemblyOutput x, AssemblyOutput y)
        {
            return x.Page - y.Page;
        }
    }
}
